
import React, { Component, cloneElement, PureComponent } from 'react';
import PropTypes from 'prop-types';

import { rectIsCrossOrContain, rectIsInFrontOnHor, rectIsInFrontOnVer, extendRect } from 'rect-rel';












/* 
# Affecter : 影响者

## 特性：
根据子元素与其与特定1个或者多位置的距离对子元素设置样式;

## props
affectAnchors : [{x:number,y:number}]?    影响锚点在Affecter的视口上的偏移坐标；
usePixelCoordInAffecter ?:boolean 表示affectAnchors中的坐标单位是否是像素，当为 false 或者未定义时，affectAnchors 中的 x 表示 影响锚点在X轴方向上相对于Affecter的宽度的偏移比例，y 表示 影响锚点在Y轴方向上相对于Affecter的高度的偏移比例；
itemAnchor ?:{x:ScaleNumber,y:ScaleNumber} = {x:0.5,y:0.5}      项目的锚点，用于计算与影响锚点之间的距离，默认值是{x:0.5,y:0.5}
transforms ?: [ (distance:{x:number,y:number},index:number,distanceArr:[{x:number,y:number}],,itemRect :{x:number,y:number,width:number,height:number},itemElement?:Element,containerElement?:Element)=>{x:number,y:number} ]    转换函数数组，里面包含的函数是用来转换项目元素的锚点与影响锚点之间的距离坐标，并返回转换后的距离坐标对象；
getItemAffectStyle ?: (distanceArr : [{x:number,y:number}],itemElement?:Element,containerElement?:Element,itemRowCol:{row:number,col:number,index:number},itemRect)=>StyleObject   获得项目影响样式的回调函数；
getItemInitStyle ?: (element,index,computeAffectStyleForItem)=>StyleObject      获得元素的初始样式（比如：用于布局的样式）的回调函数，只在初始化时为每个元素调用并设置一次；

wrapChildren  ?:boolean     表示是否包装子元素；
wrapClass  ?:React的calssName类型      定义包装元素的类
wrapStyle   ?:React的style类型      定义包装元素的样式

loopType ?: "Hor" | "Ver" | "All" | "false"     定义循环类型
wrapSpace ?:number    定义循环包之间的间隔，默认值为0

onScroll ?:function Affecter 的滑动事件处理函数；当返回 true 值时，不会执行 Affecter 的默认操作；
renderExtendRadius  ?:number    项目的渲染范围的扩展半径；





### 性能优化相关的props
throttleDelay ?:number 节流阀体的时间限，即该数值表示多长时间内不允许触发第2次更新
throttleStep  ?:number  节流阀体的步长限，即该数值表示多长的滑动距离才能触发第2次更新


### 计算循环(ComputeLoop)独有的Props
ItemType : ReactComponent   计算循环的项目类型
itemSize :{width,height}    项目的尺寸
horSpace :nunber      项目间的水平间距
verSpace :number      项目间的垂直间距
itemDataArr :Array     项目的数据数组
roundRowCount :number   单个循环周期内的行数
roundColCount :number   单个循环周期内的列数
*/







class Affecter extends PureComponent {

    constructor(props) {

        super(props);
        this.state = {
            viewRect: {
                x: 0,
                y: 0,
                width: 0,
                height: 0
            }
        };

        this.loopWrapArr = [];

        //性能优化
        this.thenTime = new Date();

        this.scrollHandle = this.scrollHandle.bind(this);
        this.computeAffectStyleForItem = this.computeAffectStyleForItem.bind(this);
        this.computeDistanceToAffecterFromItem = this.computeDistanceToAffecterFromItem.bind(this);
        this.itemIsInRenderRange = this.itemIsInRenderRange.bind(this);
        this.setThisDom = this.setThisDom.bind(this);
        this.setWrapDom = this.setWrapDom.bind(this);
        this.setLoopWrap = this.setLoopWrap.bind(this);

        //计算循环
        this.computeItemLayoutStyle = this.computeItemLayoutStyle.bind(this);
        this.computeAffectStyleForComputeLoopItem = this.computeAffectStyleForComputeLoopItem.bind(this);
        this.computeItemIndex = this.computeItemIndex.bind(this);


        this.updateForProps(props);
        this.updateForPropsState(props, this.state);

    }

    componentDidMount() {
        this.updateViewRect(true);
    }



    componentWillReceiveProps(nextProps) {
        this.updateForProps(nextProps);
    }

    componentWillUpdate(nextProps, nextState) {
        this.updateForPropsState(nextProps, nextState)
    }

    updateForPropsState(nextProps, nextState) {
        let { renderExtendRadius, ItemType } = nextProps;

        let viewRect = nextState.viewRect;
        let itemRenderRange = extendRect(viewRect, renderExtendRadius, renderExtendRadius);
        this.itemRenderRange = itemRenderRange;

        if (ItemType) {
            this.coordIndexRange = this.computeCoordIndexRange(itemRenderRange, this.itemAreaSize, nextProps.loopType, viewRect);
        }
    }




    /**
     * 此方法主要用于行初始化工作
     * @param newPops  新的Props
     */
    updateForProps(newProps) {
        let { getItemInitStyle, children, itemAnchor, wrapChildren, loopType, ItemType } = newProps;

        this.haveWrap = !ItemType && (wrapChildren || loopType);
        itemAnchor.x = itemAnchor.x == undefined ? 0.5 : itemAnchor.x;
        itemAnchor.y = itemAnchor.y == undefined ? 0.5 : itemAnchor.y;
        this.itemAnchor = itemAnchor;


        if (ItemType) {
            this.updateForComputeLoopWithProps(newProps);
        } else if (getItemInitStyle) {

            let computeAffectStyleForItem = this.computeAffectStyleForItem;
            let newChildrens = React.Children.map(children, function (currentElement, index) {
                let style = currentElement.props.style;
                let initStyleObj = getItemInitStyle(currentElement, index, computeAffectStyleForItem);
                let finalStyle = { ...style, ...initStyleObj };
                return cloneElement(currentElement, { style: finalStyle });
            });
            this.newChildrens = newChildrens;

        } else {
            this.newChildrens = children;
        }



    }








    /**
     * 更新滑动的位置信息
     */
    updateViewRect(isInit) {

        let { affectAnchors, loopType, ItemType, throttleStep } = this.props;

        if (affectAnchors || loopType) {

            let thisDom = this.thisDom;
            let newX = thisDom.scrollLeft;
            let newY = thisDom.scrollTop;



            let viewRect = this.state.viewRect;
            let stepX = newX - viewRect.x;
            let stepY = newY - viewRect.y;

            if ((Math.max(Math.abs(stepX), Math.abs(stepY)) < throttleStep) && !isInit) {
                return;
            }

            if (loopType) {


                let isBeginX = thisDom.scrollLeft <= 0;
                let isBeginY = thisDom.scrollTop <= 0;

                let allowHor = false;
                let allowVer = false;

                switch (loopType) {
                    case "All": {
                        allowHor = true;
                        allowVer = true;
                        break;
                    }

                    case "Hor": {
                        allowHor = true;
                        break;
                    }

                    case "Ver": {
                        allowVer = true;
                        break;
                    }
                }


                let needScrollX = allowHor && isBeginX && stepX < 0;
                let needScrollY = allowVer && isBeginY && stepY < 0;



                if (needScrollX || needScrollY) {

                    let scrollLengthX = 0;
                    let scrollLengthY = 0;

                    if (ItemType) {
                        scrollLengthX = needScrollX ? this.roundSize.width : 0;
                        scrollLengthY = needScrollY ? this.roundSize.height : 0;
                    } else {
                        let wrapData = this.getLoopWrapData();
                        scrollLengthX = needScrollX ? (wrapData.width + wrapData.wrapSpace) : 0;
                        scrollLengthY = needScrollY ? (wrapData.height + wrapData.wrapSpace) : 0;
                    }

                    thisDom.scrollBy(scrollLengthX, scrollLengthY);
                } else {

                    this.setState({
                        viewRect: {
                            x: newX,
                            y: newY,
                            width: thisDom.clientWidth,
                            height: thisDom.clientHeight,
                            stepX: stepX,
                            stepY: stepY
                        }
                    });

                }



            } else {

                this.setState({
                    viewRect: {
                        x: newX,
                        y: newY,
                        width: thisDom.clientWidth,
                        height: thisDom.clientHeight
                    }
                });

            }


        }

    }



    /**
     * 计算元素被影响的样式
     * @param itemElement ?: Dom元素       项目的Dom节点
     * @param orItemRect ?:{x:number,y:number,width:number,height:number}   包含元素的位置和长宽的对象
     * @param itemRowCol :{row:number,col:number,index:number}     包含项目的行号、列号、序号的对象
     * @returns    React的样式对象
     */
    computeAffectStyleForItem(itemElement, orItemRect, itemRowCol) {

        let affectStyle = null;


        let itemRect = null;
        let wrapDom = null;
        if (itemElement) {
            wrapDom = itemElement.parentNode;
            itemRect = {
                x: itemElement.offsetLeft,
                y: itemElement.offsetTop,
                width: itemElement.clientWidth,
                height: itemElement.clientHeight
            };
        } else if (orItemRect) {
            itemRect = orItemRect;
        }

        let { affectAnchors, usePixelCoordInAffecter, getItemAffectStyle } = this.props;


        if (itemRect && affectAnchors && getItemAffectStyle) {

            let initDistanceArr = this.getDistancesToAffecterFromItem(itemRect, affectAnchors, usePixelCoordInAffecter, wrapDom);
            initDistanceArr.publicData = {};    // 所有距离坐标共享的数据
            let transDistanceArr = initDistanceArr;
            let transforms = this.props.transforms;
            let thisDom = this.thisDom;

            if (transforms) {
                transDistanceArr = transforms.reduce(function (preDistanceArr, currentTransform) {

                    let publicData = preDistanceArr.publicData;
                    let newDistanceArr = preDistanceArr.map(function (distance, index, distanceArr) {
                        return currentTransform(distance, index, distanceArr, itemRect, itemElement, thisDom);
                    }, publicData);
                    newDistanceArr.publicData = publicData;

                    return newDistanceArr;
                }, initDistanceArr);
            }
            affectStyle = getItemAffectStyle(transDistanceArr, itemElement, this.thisDom, itemRowCol, itemRect);

        }


        return affectStyle;
    }



    /**
     * 获得项目元素与所有影响锚点之间的距离坐标
     * @param itemRect : {x = 0,y = 0,width = 0,height = 0}   包含项目元素的位置和长宽的对象
     * @param affectAnchors : {x = 0,y = 0}   影响坐标
     * @param usePixelCoordInAffecter : boolean   影响坐标的单位是否是像素
     * @param wrapDom ? 包装元素的Dom
     * @returns [{x ?: number, y ?: number}]      返回项目元素与影响锚点之间的距离坐标
     */
    getDistancesToAffecterFromItem(itemRect, affectAnchors, usePixelCoordInAffecter, wrapDom) {

        let computeDistanceToAffecterFromItem = this.computeDistanceToAffecterFromItem;
        let viewRect = this.state.viewRect;
        let finalItemRect = itemRect;

        if (this.haveWrap && wrapDom) {
            finalItemRect.x += wrapDom.offsetLeft;
            finalItemRect.y += wrapDom.offsetTop;
        }

        let itemAnchor = this.itemAnchor;
        let distanceArr = affectAnchors.map(function (affectAnchor, index, arr) {
            return computeDistanceToAffecterFromItem(affectAnchor, usePixelCoordInAffecter, viewRect, itemAnchor, finalItemRect);
        });

        return distanceArr;
    }


    /**
     * 提供给item的工具方法，用于判断item是否在可视范围内
     * @param itemElement : Dom   item的Dom
     * @returns {boolean}
     */
    itemIsInRenderRange(itemElement) {

        let itemX = itemElement.offsetLeft;
        let itemY = itemElement.offsetTop;

        if (this.haveWrap) {
            let wrapDom = itemElement.parentNode;
            itemX += wrapDom.offsetLeft;
            itemY += wrapDom.offsetTop;
        }


        let itemRect = {
            x: itemX,
            y: itemY,
            width: itemElement.clientWidth,
            height: itemElement.clientHeight
        };

        return rectIsCrossOrContain(this.itemRenderRange, itemRect);

    }



    /**
     * 计算项目元素与影响锚点之间的距离坐标
     * @param affectAnchor : {x ?: number, y ?: number}      影响锚点的比例坐标
     * @param usePixelCoordInAffecter ?:boolean 表示affectAnchors中的坐标单位是像素
     * @param viewRect : {x:number,y:number,width:number,height:number}   容器的视口的滑动偏移矩形
     * @param itemAnchor : {x : number, y : number}      项目的锚点
     * @param {x = 0,y = 0,width = 0,height = 0}    项目的位置和长宽
     * @returns {x ?: number, y ?: number}      返回项目元素与影响锚点之间的距离坐标
     */
    computeDistanceToAffecterFromItem(affectAnchor, usePixelCoordInAffecter, viewRect, itemAnchor, { x = 0, y = 0, width = 0, height = 0 }) {

        let distance = { x: 0, y: 0 };


        let { x: affecterX = 0, y: affecterY = 0 } = this.computeAffectAnchorAbsCoord(affectAnchor, usePixelCoordInAffecter, viewRect);

        let anchorCoordX = x + itemAnchor.x * width;
        let anchorCoordY = y + itemAnchor.y * height;

        distance.x = anchorCoordX - affecterX;
        distance.y = anchorCoordY - affecterY;
        return distance;
    }


    /**
     * 计算影响锚点的绝对坐标
     * @param affectAnchor
     * @param usePixelCoordInAffecter
     * @param viewRect
     */
    computeAffectAnchorAbsCoord(affectAnchor, usePixelCoordInAffecter, viewRect) {
        let absCoord = {};

        let affectAnchorX = affectAnchor.x;
        let affectAnchorY = affectAnchor.y;

        let { x: offsetX, y: offsetY, width: relLengthX, height: relLengthY } = viewRect;

        if (affectAnchorX) {
            let relCoordX = usePixelCoordInAffecter ? affectAnchorX : affectAnchorX * relLengthX;
            absCoord.x = offsetX + relCoordX;
        }

        if (affectAnchorY) {
            let relCoordY = usePixelCoordInAffecter ? affectAnchorY : affectAnchorY * relLengthY;
            absCoord.y = offsetY + relCoordY;
        }

        return absCoord;
    }







    /**
     * 滑动事件处理函数
     * @param event 滑动事件对象
     */
    scrollHandle(event) {

        let onScroll = this.props.onScroll;


        /*
        在以下情况不会触发 updateViewRect()
        - Affecter 组件上有 onScroll 事件处理函数 且 该 onScroll 返回 true 值
        */
        if (!(onScroll && onScroll(event))) {

            //节流
            var now = new Date();
            if (now - this.thenTime >= this.props.throttleDelay) {
                this.updateViewRect();
                this.thenTime = now;
            }

        }
    }





    setThisDom(element) {
        this.thisDom = element;
    }

    setWrapDom(element) {
        this.wrapDom = element;
    }

    setLoopWrap(component) {
        this.loopWrapArr.push(component);
    }

    getLoopWrapData() {
        let wrapComponent = this.loopWrapArr[0];
        return wrapComponent.wrapData;
    }







    render() {

        let { throttleDelay, throttleStep, ItemType, itemSize, horSpace, verSpace, itemDataArr, roundRowCount, roundColCount, onScroll, style, affectAnchors, usePixelCoordInAffecter, itemAnchor, transforms, getItemInitStyle, getItemAffectStyle, wrapChildren, wrapClass, wrapStyle, loopType, wrapSpace, children, renderExtendRadius, ...otherProps } = this.props;

        let newChildrens = null;
        let finalElement = null;
        let rootStyle = null;

        if (ItemType) { //计算循环模式

            newChildrens = [];
            rootStyle = { ...style, position: "relative" };

            if (itemDataArr && itemDataArr.length > 0) {

                let { rowStart, rowEnd, colStart, colEnd } = this.coordIndexRange;
                let computeItemLayoutStyle = this.computeItemLayoutStyle;
                let computeAffectStyleForComputeLoopItem = this.computeAffectStyleForComputeLoopItem;
                let computeItemIndex = this.computeItemIndex;


                let newIndexKeyArr = this.creationIndexKeyArr();
                for (let row = rowStart; row <= rowEnd; row++) {
                    for (let col = colStart; col <= colEnd; col++) {

                        let layoutStyle = computeItemLayoutStyle(row, col);
                        let affectStyle = computeAffectStyleForComputeLoopItem(row, col);
                        let childrenStyle = { position: "absolute", ...layoutStyle, ...affectStyle };
                        let itemIndex = computeItemIndex(row, col);
                        let itemData = itemDataArr[(itemIndex % itemDataArr.length)];

                        let itemKey = this.indexKeyArr[itemIndex].pop();
                        if (!itemKey) {
                            let currentDate = new Date();
                            itemKey = `${row}${col}${currentDate.getTime()}`;
                        }


                        let childrenProps = {
                            style: childrenStyle,
                            row: row,
                            col: col,
                            itemIndex: itemIndex,
                            itemData: itemData,
                            key: itemKey
                        };

                        newChildrens.push(<ItemType {...childrenProps} />);
                        newIndexKeyArr[itemIndex].push(itemKey);
                    }
                }

                this.indexKeyArr = newIndexKeyArr;
            }

            finalElement = <div {...otherProps} style={rootStyle} onScroll={this.scrollHandle} ref={this.setThisDom} >{newChildrens}</div>;


        } else {



            //克隆节点是为了让子组件也跟着刷新
            let itemIsInRenderRange = this.itemIsInRenderRange;
            let computeAffectStyleForItem = this.computeAffectStyleForItem;
            newChildrens = React.Children.map(this.newChildrens, function (currentElement, index) {
                return cloneElement(currentElement, { itemIsInRenderRange, computeAffectStyleForItem });
            });


            rootStyle = { ...style, position: "relative" };     //如果元素的父元素没有被定位，即没有设置 position，则元素的 offsetLeft 和 offsetTop 即是相对于document元素的；



            if (loopType) {     //循环模式

                let viewRect = this.state.viewRect;
                let direcX = viewRect.stepX > 0;
                let direcY = viewRect.stepY > 0;

                let newWrapStyle = { ...wrapStyle, position: "absolute" };

                let wrapProps = {
                    className: wrapClass,
                    style: newWrapStyle,
                    viewRect: viewRect,
                    direcX: direcX,
                    direcY: direcY,
                    loopType: loopType,
                    wrapSpace: wrapSpace,
                    ref: this.setLoopWrap
                };

                let rootProps = { ...otherProps, onScroll: this.scrollHandle, ref: this.setThisDom };



                if (loopType === "All") {   //任意方向循环模式
                    finalElement = (
                        <div {...rootProps} style={rootStyle}  >
                            <LoopWrap {...wrapProps} rowCol="00" key="00" >{newChildrens}</LoopWrap>
                            <LoopWrap {...wrapProps} rowCol="01" key="01" >{newChildrens}</LoopWrap>
                            <LoopWrap {...wrapProps} rowCol="10" key="10" >{newChildrens}</LoopWrap>
                            <LoopWrap {...wrapProps} rowCol="11" key="11" >{newChildrens}</LoopWrap>
                        </div>
                    );


                } else {    //单方向循环模式
                    finalElement = (
                        <div {...rootProps} style={rootStyle} >
                            <LoopWrap {...wrapProps} rowCol="00" key="00" >{newChildrens}</LoopWrap>
                            <LoopWrap {...wrapProps} rowCol="01" key="01" >{newChildrens}</LoopWrap>
                        </div>
                    );
                }

            } else if (wrapChildren) {      //包裹模式
                finalElement = (
                    <div {...otherProps} style={rootStyle} onScroll={this.scrollHandle} ref={this.setThisDom} >
                        <div className={wrapClass} style={wrapStyle} ref={this.setWrapDom} >{newChildrens}</div>
                    </div>
                );

            } else {    //正常模式
                finalElement = <div {...otherProps} style={style} onScroll={this.scrollHandle} ref={this.setThisDom} >{newChildrens}</div>;
            }


        }


        return finalElement;
    }















    //计算循环

    /**
     * 计算布局样式
     * @param row :number  坐标行号
     * @param col :number  坐标列号
     * @returns {position:"absolute",left: string, top: string，width: string,height: string}  项目的布局样式
     */
    computeItemLayoutStyle(row, col) {
        let { x, y } = this.computeItemCoord(row, col);
        let { width, height } = this.props.itemSize;
        return {
            position: "absolute",
            left: `${x}px`,
            top: `${y}px`,
            width: `${width}px`,
            height: `${height}px`
        };
    }



    /**
     * 计算项目的矩形
     * @param row :number  坐标行号
     * @param col :number  坐标列号
     * @returns {x: number, y: number,width:number,height:number}   计算项目的矩形
     */
    computeItemRect(row, col) {
        let itemCoord = this.computeItemCoord(row, col);
        let itemRect = { ...itemCoord, ...this.props.itemSize };
        return itemRect;
    }


    /**
     * 计算项目的坐标
     * @param row :number  坐标行号
     * @param col :number  坐标列号
     * @returns {x: number, y: number}    计算项目的坐标
     */
    computeItemCoord(row, col) {
        let { width, height } = this.itemAreaSize;
        return {
            x: col * width,
            y: row * height
        };
    }




    /**
     * 当props更新时，更新与ComputeLoop（计算循环）相关的配置
     * @param newProps      新的props
     */
    updateForComputeLoopWithProps(newProps) {
        let { itemSize, horSpace, verSpace, itemDataArr, roundRowCount, roundColCount, getItemData } = newProps;

        let itemAreaWidth = itemSize.width + horSpace;
        let itemAreaHeight = itemSize.height + verSpace;

        this.itemAreaSize = {
            width: itemAreaWidth,
            height: itemAreaHeight
        };

        let rowCount = roundRowCount;
        let colCount = roundColCount;
        let roundItemCount = itemDataArr.length;

        if (roundRowCount) {
            colCount = Math.ceil(roundItemCount / roundRowCount);
        } else if (roundColCount) {
            rowCount = Math.ceil(roundItemCount / roundColCount);
        }


        this.roundRowCount = rowCount;
        this.roundColCount = colCount;
        this.roundItemCount = rowCount * colCount;
        this.indexKeyArr = this.creationIndexKeyArr();


        this.roundSize = {
            width: itemAreaWidth * colCount,
            height: itemAreaHeight * rowCount
        };


    }






    /**
     * 根据给定的矩形计算坐标行列号的范围
     * @param renderRect   渲染矩形
     * @param itemAreaSize  项目的尺寸
     * @param loopType   循环类型
     * @param viewRect  视口的矩形
     * 
     * 注意：
     * 行列号是 计算循环为 项目设置的网络坐标，每个网格对应一个项目的位置；
     */
    computeCoordIndexRange(renderRect, itemAreaSize, loopType, viewRect) {

        let { x, y, width, height } = renderRect;
        let { width: itemAreaWidth, height: itemAreaHeight } = itemAreaSize;
        let { stepX, stepY } = viewRect;

        let direX = stepX >= 0;
        let direY = stepY >= 0;

        let right = x + width;
        let bottom = y + height;


        let computeCoordIndex = this.computeCoordIndex;

        let rowStart = 0;
        let rowEnd = 0;
        let colStart = 0;
        let colEnd = 0;



        /*
        实际上 rowCount 和 colCount 比 实际的 行数少1，但为了提高性能，这里不再加上这个差值，因为后面的计算为把这个差值给弥补过来；
        通过 rowCount 和 colCount 计算 rowEnd 和 colEnd 可以保证，每次渲染的行列数相同
        */
        let startX = renderRect.x < 0 ? 0 : renderRect.x;
        let startY = renderRect.y < 0 ? 0 : renderRect.y;
        switch (loopType) {

            case "Ver": {
                rowStart = computeCoordIndex(startY, itemAreaHeight);
                let rowCount = computeCoordIndex(renderRect.height, itemAreaHeight);
                rowEnd = rowStart + rowCount;
                let colCount = computeCoordIndex(viewRect.width, itemAreaWidth);
                colEnd = colStart + colCount;

                (!direY && rowStart > 0) ? rowStart-- : rowEnd++;

                break;
            }



            case "Hor": {
                let rowCount = computeCoordIndex(viewRect.height, itemAreaHeight);
                rowEnd = rowStart + rowCount;
                colStart = computeCoordIndex(startX, itemAreaWidth);
                let colCount = computeCoordIndex(renderRect.width, itemAreaWidth);
                colEnd = colStart + colCount;

                (!direX && colStart > 0) ? colStart-- : colEnd++;

                break;
            }


            default: {
                rowStart = computeCoordIndex(startY, itemAreaHeight);
                let rowCount = computeCoordIndex(renderRect.height, itemAreaHeight);
                rowEnd = rowStart + rowCount;
                colStart = computeCoordIndex(startX, itemAreaWidth);
                let colCount = computeCoordIndex(renderRect.width, itemAreaWidth);
                colEnd = colStart + colCount;

                (!direX && colStart > 0) ? colStart-- : colEnd++;
                (!direY && rowStart > 0) ? rowStart-- : rowEnd++;

            }


        }


        return { rowStart, rowEnd, colStart, colEnd };
    }





    /**
     * 根据位置计算坐标index
     * @param location : number    位置
     * @param areaLength : number   单位长度
     * 注意：
     * index从零开始，index为零的item的坐标在坐标原点；
     */
    computeCoordIndex(location, areaLength) {
        return Math.floor(location / areaLength);
    }



    /**
     * 根据行列号计算项目的itemIndex
     * @param row : number    行号
     * @param col : number    列号
     * 注意：
     * itemIndex表示的是项目在计算循环的循环单元（循环周期）内的序号；所以如果2个item的 itemIndex 相同，则它们接收的 itemData 也是相同的；
     */
    computeItemIndex(row, col) {
        let { roundRowCount, roundColCount } = this.props;

        let rowCount = this.roundRowCount;
        let colCount = this.roundColCount;

        let abs = Math.abs;
        let roundRow = abs(row) % rowCount;
        let roundCol = abs(col) % colCount;


        let itemIndex = 0;
        if (roundRowCount) {
            itemIndex = roundCol * rowCount + roundRow;
        } else {
            itemIndex = roundRow * colCount + roundRow;
        }


        return itemIndex;

    }


    /**
     * 计算ComputeLoop项目的影响样式
     * @param row : number 行号
     * @param col : number 列号
     * @param itemIndex : number    项目序号
     * @returns 样式对象
     */
    computeAffectStyleForComputeLoopItem(row, col, itemIndex) {
        let itemRect = this.computeItemRect(row, col);
        return this.computeAffectStyleForItem(null, itemRect, { row, col, index: itemIndex });
    }



    creationIndexKeyArr() {
        let indexKeyArr = [];
        let roundItemCount = this.roundItemCount;
        for (let index = 0; index < roundItemCount; index++) {
            indexKeyArr.push([]);
        }
        return indexKeyArr;
    }



}







// Affecter的props的类型说明
Affecter.propTypes = {
    affectAnchors: PropTypes.arrayOf(PropTypes.shape({
        x: PropTypes.number,
        y: PropTypes.number
    })),
    itemAnchor: PropTypes.shape({
        x: PropTypes.number,
        y: PropTypes.number
    }),
    transforms: PropTypes.arrayOf(PropTypes.func),
    getItemAffectStyle: PropTypes.func,
    getItemInitStyle: PropTypes.func,
    wrapChildren: PropTypes.bool,
    renderExtendRadius: PropTypes.number,

    //   性能优化
    throttleDelay: PropTypes.number,
    throttleStep: PropTypes.number,

    //   计算循环
    itemSize: PropTypes.shape({
        width: PropTypes.number,
        height: PropTypes.number
    }),
    horSpace: PropTypes.number,
    verSpace: PropTypes.number,
    itemDataArr: PropTypes.array,
    roundRowCount: PropTypes.number,
    roundColCount: PropTypes.number
};

// Affecter的props的默认值
Affecter.defaultProps = {
    renderExtendRadius: 50,
    itemAnchor: { x: 0.5, y: 0.5 },
    throttleDelay: 40,
    throttleStep: 2
};















/**
 * LoopWrap 循环包
 * @props viewRect : {x:number,y:number,width:number,height:number}   容器的视口的滑动偏移矩形
 * @props direcX  : boolean  X轴的滑动方向
 * @props direcY : boolean  Y轴的滑动方向
 * @props loopType ? : "Hor" | "Ver" | "All" | "false"     定义循环类型
 * @props rowCol : "00" | "01" | "10" | "11"      表示wrap位置的标识符
 * @props wrapSpace  ? : number    定义循环包之间的间隔，默认值为0
 */

class LoopWrap extends Component {

    constructor(props) {
        super(props);
        this.setWrapDom = this.setWrapDom.bind(this);

        this.wrapData = { wrapSpace: props.wrapSpace };
        this.adjustStyle = {};
    }

    componentDidMount() {
        this.configInitStyle(this.wrapDom);
    }

    componentWillReceiveProps(nextProps) {
        this.computeAdjustStyle(nextProps);
    }


    computeAdjustStyle(props) {

        let { viewRect, direcX, direcY, wrapSpace, loopType } = props;


        let wrapDom = this.wrapDom;

        let x = wrapDom.offsetLeft;
        let y = wrapDom.offsetTop;
        let width = wrapDom.scrollWidth;
        let height = wrapDom.scrollHeight;

        let wrapRect = { x, y, width, height };




        let outViewPropX = null;
        let divisorX = null;

        if (direcX) {
            outViewPropX = rectIsInFrontOnHor(wrapRect, viewRect);
            divisorX = 1;
        } else {
            outViewPropX = rectIsInFrontOnHor(viewRect, wrapRect);
            divisorX = -1;
        }


        let outViewPropY = null;
        let divisorY = null;
        if (direcY) {
            outViewPropY = rectIsInFrontOnVer(wrapRect, viewRect);
            divisorY = 1;
        } else {
            outViewPropY = rectIsInFrontOnVer(viewRect, wrapRect);
            divisorY = -1;
        }



        let newX = -1;
        let newY = -1;

        switch (loopType) {
            case "Hor": {

                if (outViewPropX) {
                    let lengthX = width + wrapSpace;
                    newX = x + 2 * lengthX * divisorX;
                }
                break;
            }

            case "Ver": {
                if (outViewPropY) {
                    let lengthY = height + wrapSpace;
                    newY = y + 2 * lengthY * divisorY;
                }
                break;
            }

            case "All": {
                if (outViewPropX) {
                    let lengthX = width + wrapSpace;
                    newX = x + 2 * lengthX * divisorX;
                }

                if (outViewPropY) {
                    let lengthY = height + wrapSpace;
                    newY = y + 2 * lengthY * divisorY;
                }
                break;
            }
        }


        let wrapData = this.wrapData;
        wrapData.height = height;
        wrapData.width = width;
        wrapData.wrapSpace = wrapSpace;

        //Dom 的 scrollLeft 和 scrollTop 滑不到负值，只能为正值；
        if (newX >= 0) {
            wrapData.x = newX;
        }

        if (newY >= 0) {
            wrapData.y = newY;
        }

        this.setWrapData(wrapData);
    }


    setWrapData(wrapData) {
        this.wrapData = wrapData;

        let adjustStyle = this.adjustStyle;
        adjustStyle.left = `${wrapData.x}px`;
        adjustStyle.top = `${wrapData.y}px`;
        adjustStyle.width = `${wrapData.width}px`;
        adjustStyle.height = `${wrapData.height}px`;
        this.adjustStyle = adjustStyle;
    }


    setWrapDom(element) {
        this.wrapDom = element;
    }


    configInitStyle(wrapElement) {
        let width = wrapElement.scrollWidth;
        let height = wrapElement.scrollHeight;
        let left = 0;
        let top = 0;

        let { rowCol, loopType, wrapSpace } = this.props;

        switch (loopType) {
            case "Hor": {
                if (rowCol === "01") {
                    left = width + wrapSpace;
                }
                break;
            }

            case "Ver": {
                if (rowCol === "01") {
                    top = height + wrapSpace;
                }
                break;
            }

            case "All": {

                switch (rowCol) {
                    case "01": {
                        left = width + wrapSpace;
                        break;
                    }
                    case "10": {
                        top = height + wrapSpace;
                        break;
                    }
                    case "11": {
                        left = width + wrapSpace;
                        top = height + wrapSpace;
                        break;
                    }

                }

                break;
            }
        }


        let wrapData = this.wrapData;
        wrapData.x = left;
        wrapData.y = top;
        wrapData.width = width;
        wrapData.height = height;

        this.setWrapData(wrapData);
    }

    render() {
        let { viewRect, direcX, direcY, loopType, style, rowCol, wrapSpace, ...otherProps } = this.props;
        let newStyle = { ...style, ...this.adjustStyle };

        return <div {...otherProps} style={newStyle} ref={this.setWrapDom} ></div>;
    }

}


/**
 * LoopWrap 的 props 的默认值；
 */
LoopWrap.defaultProps = {
    wrapSpace: 0
};














/*
# AffectedItem ：影响者的项目

## props

以下props是由使用者来定义
renderAll ? : boolean   每次刷新是否渲染所有元素，默认是否，只渲染 渲染矩形 内的元素；

以下props由Affecter组件负责传送，用户不用传送
computeAffectStyleForItem : (itemElement ?: Dom,orItemRect ?:{x = 0,y = 0,width = 0,height = 0})=>StyleObject    计算元素被影响的样式
itemIsInRenderRange   : (itemElement : Dom)=>boolean      用于判断该元素是否在渲染矩形内；
## 使用说明
AffectedItem 组件专门用作 Affecter 组件的子组件，使用格式为：
```
<Affecter>
    <AffectedItem>
        <p>郭斌勇</p>
    </AffectedItem>
</Affecter>
```

您也可创建自己的AffectedItem组件，只需要在需要设置样式时通过 this.props.computeAffectStyleForItem 函数获取新的样式即可；

*/
class AffectedItem extends Component {
    constructor(props) {
        super(props);
        this.setThisDom = this.setThisDom.bind(this);
    }


    componentDidMount(){
        // 兼容低版本React的代码
        this.forceUpdate();
    }

    shouldComponentUpdate(nextProps, nextState) {
        return nextProps.renderAll || nextProps.itemIsInRenderRange(this.thisDom);
    }

    setThisDom(element) {
        this.thisDom = element;
    }

    render() {
        let { computeAffectStyleForItem, style, itemIsInRenderRange, renderAll, ...otherProps } = this.props;
        let affectStyle = computeAffectStyleForItem(this.thisDom);
        let newStyle = { ...style, ...affectStyle };

        return <div {...otherProps} style={newStyle} ref={this.setThisDom}></div>;
    }


}

















/*
ComputeLoopItem 计算循环项目的创建函数
@param ItemContentType  计算项目的内容组件


# ComputeLoopItem
@props row  行号
@props col  列号
@props itemIndex    项目序号
@props style    样式
@props itemData    item 的数据





# ItemContentType
@props row  行号
@props col  列号
@props itemIndex    项目序号
@props itemData    item 的数据


 */
function computeLoopItemTypeCreater(ItemContentType) {

    class ComputeLoopItem extends Component {

        render() {
            let { style, ...otherProps } = this.props;
            return (
                <div style={style}>
                    <ItemContentType {...otherProps} />
                </div>
            );
        }

    }


    return ComputeLoopItem;

}





export { Affecter, AffectedItem, computeLoopItemTypeCreater }